
jarkati = jarkati or {}
jarkati.modpath = minetest.get_modpath("jarkati")

-- These match values in the realm-control mod.
jarkati.REALM_START = 3600
jarkati.REALM_END = 3900
jarkati.SEA_LEVEL = 3740
jarkati.LAVA_LEVEL = 3610

jarkati.biomes = {}
jarkati.decorations = {}

-- Public API function.
function jarkati.register_layer(data)
	local td = table.copy(data)

	assert(type(td.node) == "string")

	-- Convert string name to content ID.
	td.cid = minetest.get_content_id(td.node)

	td.min_level = td.min_level or 1
	td.max_level = td.max_level or 1

	assert(td.min_level <= td.max_level)

	td.min_depth = td.min_depth or 1
	td.max_depth = td.max_depth or 1

	assert(td.min_depth <= td.max_depth)

	jarkati.biomes[#jarkati.biomes + 1] = td
end

-- Public API function.
function jarkati.register_decoration(data)
	local td = table.copy(data)

	-- Convert single node to table.
	if type(td.nodes) == "string" then
		td.nodes = {td.nodes}
	end

	if type(td.replace_surface) == "string" then
		td.replace_surface = {td.replace_surface}
	end

	-- Used only with the `nodes` parameter.
	td.param2 = data.param2 or {0}

	assert(type(td.nodes) == "table" or type(td.schematic) == "string" or type(td.schematic) == "table")
	assert(type(td.probability) == "number")
	assert(td.probability >= 1)

	-- Ensure all node names are actually registered!
	if td.nodes then
		for k, v in ipairs(td.nodes) do
			assert(minetest.registered_nodes[v])
		end
	end

	if not td.all_ceilings and not td.all_floors then
		td.ground_level = true
	end

	-- Default schematic parameters.
	td.rotation = data.rotation or "0"
	td.replacements = data.replacements or {}
	td.force_placement = data.force_placement or false
	td.flags = data.flags or ""

	-- Placement Y-offset is always 0 (ignored) if `place_center_y` is specified.
	if td.flags:find("place_center_y") then
		td.place_offset_y = 0
	else
		td.place_offset_y = data.place_offset_y or 0

		-- Invert for `all_ceilings` decorations.
		if td.all_ceilings and td.place_offset_y > 0 then
			td.place_offset_y = -td.place_offset_y
		end
	end

	-- The object or schematic's aprox `radius`, when checking for flat ground.
	-- Non-zero basically means all ground in the radius must be the same height.
	td.radius = data.radius or 0

	td.y_min = data.y_min or -31000
	td.y_max = data.y_max or 31000

	jarkati.decorations[#jarkati.decorations + 1] = td
end

jarkati.register_layer({
	node = "default:desert_sand",
	min_depth = 1,
	max_depth = 2,
	min_level = 1,
	max_level = 1,
})

jarkati.register_layer({
	node = "default:sand",
	min_depth = 3,
	max_depth = 3,
	min_level = 1,
	max_level = 1,
})

jarkati.register_layer({
	node = "default:desert_cobble",
	min_depth = 7,
	max_depth = 7,
	min_level = 1,
	max_level = 1,
})

jarkati.register_layer({
	node = "default:sandstone",
	min_depth = 4,
	max_depth = 6,
	min_level = 1,
	max_level = 1,
})

jarkati.register_decoration({
	nodes = "stairs:slab_desert_cobble",
	probability = 700,
	place_on = {"default:desert_sand"},
})

jarkati.register_decoration({
	nodes = "aloevera:aloe_plant_04",
	probability = 1500,
	place_on = {"default:desert_sand"},
        replace_surface = "default:dirt_with_dry_grass",
})

-- Scatter "rubble" around the bases of cliffs.
jarkati.register_decoration({
	nodes = {"default:desert_cobble", "stairs:slab_desert_cobble"},
	probability = 10,
	spawn_by = {"default:desert_stone", "default:sandstone"},
	place_on = {"default:desert_sand"},
})

jarkati.register_decoration({
	nodes = "default:dry_shrub",
	probability = 110,
	place_on = {"default:desert_sand"},
})

-- These should add a decoration to the Jarkati landscape but couldn't get it to work. I modified the depends to use flowers but the way the flowers mod is set up seems to conflict with the way the jarkati mod interprets "nodes". Maybe you can look into it? I added these two items to flowers but did not otherwise put them in the mapgen. They will show up in the craftguide.
        
--jarkati.register_decoration({
--	nodes = "flowers:desertrose_red",
--	probability = 100,
--	place_on = {"default:desert_sand"},
--        replace_surface = "default:dirt_with_dry_grass",
--})

--jarkati.register_decoration({
--	nodes = "flowers:desertrose_pink",
--	probability = 100,
--	place_on = {"default:desert_sand"},
--        replace_surface = "default:dirt_with_dry_grass",
--})


jarkati.register_decoration({
	nodes = {
		"default:dry_grass_1",
		"default:dry_grass_2",
		"default:dry_grass_3",
		"default:dry_grass_4",
		"default:dry_grass_5",
	},
	param2 = {2},
	probability = 50,
	place_on = {"default:desert_sand"},
	replace_surface = "default:dirt_with_dry_grass",
})

jarkati.register_decoration({
	nodes = {
		"cavestuff:redspike1",
		"cavestuff:redspike2",
		"cavestuff:redspike3",
		"cavestuff:redspike4",
	},
	param2 = {0, 1, 2, 3},
	probability = 120,
	all_floors = true,
	place_on = {"default:desert_stone"},
})

---[[
do
	local _ = {name = "air"}
	local X = {name = "default:gravel"}
	local C1 = {name = "stairs:micro_desert_sandstone", param2 = 1}
	local C2 = {name = "stairs:micro_desert_sandstone", param2 = 2}
	local C3 = {name = "stairs:micro_desert_sandstone", param2 = 0}
	local C4 = {name = "stairs:micro_desert_sandstone", param2 = 3}
	local L = {name = "stairs:slab_desert_sandstone"}
	local R = {name = "default:desert_sandstone"}
	local S = {name = "default:desert_sand"}

	jarkati.register_decoration({
		schematic = {
			size = {x=4, y=2, z=4},
			data = {
				S, R, R, S,
				C1, L, L, C3,

				R, X, X, R,
				L, _, _, L,

				R, X, X, R,
				L, _, _, L,

				S, R, R, S,
				C2, L, L, C4,
			},
		},
		force_placement = true,
		flags = "place_center_x,place_center_z",
		rotation = "random",
    place_offset_y = -1,
    radius = 3,
		probability = 800,
		place_on = {"default:desert_sand"},

		-- Called with 'pos', the placement position of the decoration.
		custom_func = function(...)
			pm.on_wisp_vent_place(...)
		end,

    y_max = 3760,
    y_min = 3730,
	})
end
--]]

---[[
do
	local _ = {name = "air"}
	local X = {name = "default:gravel"}
	local C1 = {name = "stairs:micro_desert_sandstone", param2 = 1}
	local C2 = {name = "stairs:micro_desert_sandstone", param2 = 2}
	local C3 = {name = "stairs:micro_desert_sandstone", param2 = 0}
	local C4 = {name = "stairs:micro_desert_sandstone", param2 = 3}
	local L1 = {name = "stairs:stair_desert_sandstone", param2 = 0}
	local L2 = {name = "stairs:stair_desert_sandstone", param2 = 1}
	local L3 = {name = "stairs:stair_desert_sandstone", param2 = 2}
	local L4 = {name = "stairs:stair_desert_sandstone", param2 = 3}
	local R = {name = "default:desert_sandstone"}
	local S = {name = "default:desert_sand"}

	jarkati.register_decoration({
		schematic = {
			size = {x=4, y=2, z=4},
			data = {
				S, R, R, S,
				C1, L1, L1, C3,

				R, X, X, R,
				L2, _, _, L4,

				R, X, X, R,
				L2, _, _, L4,

				S, R, R, S,
				C2, L3, L3, C4,
			},
		},
		force_placement = true,
		flags = "place_center_x,place_center_z",
		rotation = "random",
    place_offset_y = -1,
    radius = 4,
		probability = 800,
		place_on = {"default:desert_sand"},

		-- Called with 'pos', the placement position of the decoration.
		custom_func = function(...)
			pm.on_wisp_vent_place(...)
		end,

    y_max = 3760,
    y_min = 3730,
	})
end
--]]

---[[
do
	local _ = {name = "air"}
	local X = {name = "rackstone:nether_grit"}
	local C1 = {name = "stairs:micro_desert_sandstone", param2 = 1}
	local C2 = {name = "stairs:micro_desert_sandstone", param2 = 2}
	local C3 = {name = "stairs:micro_desert_sandstone", param2 = 0}
	local C4 = {name = "stairs:micro_desert_sandstone", param2 = 3}
	local L = {name = "stairs:slab_desert_sandstone"}
	local R = {name = "default:desert_sandstone"}
	local S = {name = "default:desert_sand"}

	jarkati.register_decoration({
		schematic = {
			size = {x=4, y=2, z=4},
			data = {
				S, R, R, S,
				C1, L, L, C3,

				R, X, X, R,
				L, _, _, L,

				R, X, X, R,
				L, _, _, L,

				S, R, R, S,
				C2, L, L, C4,
			},
		},
		force_placement = true,
		flags = "place_center_x,place_center_z",
		rotation = "random",
    place_offset_y = -1,
    radius = 3,
		probability = 2500,
		place_on = {"default:desert_sand"},

    y_max = 3750,
    y_min = 3730,
	})
end
--]]

---[[
do
	local _ = {name = "air"}
	local X = {name = "rackstone:nether_grit"}
	local C1 = {name = "stairs:micro_desert_sandstone", param2 = 1}
	local C2 = {name = "stairs:micro_desert_sandstone", param2 = 2}
	local C3 = {name = "stairs:micro_desert_sandstone", param2 = 0}
	local C4 = {name = "stairs:micro_desert_sandstone", param2 = 3}
	local L1 = {name = "stairs:stair_desert_sandstone", param2 = 0}
	local L2 = {name = "stairs:stair_desert_sandstone", param2 = 1}
	local L3 = {name = "stairs:stair_desert_sandstone", param2 = 2}
	local L4 = {name = "stairs:stair_desert_sandstone", param2 = 3}
	local R = {name = "default:desert_sandstone"}
	local S = {name = "default:desert_sand"}

	jarkati.register_decoration({
		schematic = {
			size = {x=4, y=2, z=4},
			data = {
				S, R, R, S,
				C1, L1, L1, C3,

				R, X, X, R,
				L2, _, _, L4,

				R, X, X, R,
				L2, _, _, L4,

				S, R, R, S,
				C2, L3, L3, C4,
			},
		},
		force_placement = true,
		flags = "place_center_x,place_center_z",
		rotation = "random",
    place_offset_y = -1,
    radius = 4,
		probability = 2500,
		place_on = {"default:desert_sand"},

    y_max = 3750,
    y_min = 3730,
	})
end
--]]

local NOISE_SCALE = 1

-- Base terrain height (may be modified to be negative or positive).
jarkati.noise1param2d = {
	offset = 0,
	scale = 1,
	spread = {x=128*NOISE_SCALE, y=128*NOISE_SCALE, z=128*NOISE_SCALE},
	seed = 5719,
	octaves = 6,
	persist = 0.5,
	lacunarity = 2,
}

-- Modifies frequency of vertical tunnel shafts.
jarkati.noise2param2d = {
	offset = 0,
	scale = 1,
	spread = {x=64*NOISE_SCALE, y=64*NOISE_SCALE, z=64*NOISE_SCALE},
	seed = 8827,
	octaves = 6,
	persist = 0.4, -- Amplitude multiplier.
	lacunarity = 2, -- Wavelength divisor.
}

-- Mese/tableland terrain-height modifier.
jarkati.noise3param2d = {
	offset = 0,
	scale = 1,
	spread = {x=64*NOISE_SCALE, y=64*NOISE_SCALE, z=64*NOISE_SCALE},
	seed = 54871,
	octaves = 5,
	persist = 0.5,
	lacunarity = 2,
}

-- Modifies the frequency (strength) of tablelands over big area.
jarkati.noise4param2d = {
	offset = 0,
	scale = 1,
	spread = {x=128*NOISE_SCALE, y=128*NOISE_SCALE, z=128*NOISE_SCALE},
	seed = 2819,
	octaves = 3,
	persist = 0.5,
	lacunarity = 2,
}

-- Disjunction height modifier.
jarkati.noise5param2d = {
	offset = 0,
	scale = 1,
	spread = {x=512*NOISE_SCALE, y=512*NOISE_SCALE, z=512*NOISE_SCALE},
	seed = 3819,
	octaves = 6,
	persist = 0.7,
	lacunarity = 2,
}

-- Primary cavern noise.
jarkati.noise6param3d = {
	offset = 0,
	scale = 1,
	spread = {x=128*NOISE_SCALE, y=32*NOISE_SCALE, z=128*NOISE_SCALE},
	seed = 3817,
	octaves = 5,
	persist = 0.5,
	lacunarity = 2,
}

-- Vertical tunnel noise.
jarkati.noise7param3d = {
	offset = 0,
	scale = 1,
	spread = {x=8*NOISE_SCALE, y=64*NOISE_SCALE, z=8*NOISE_SCALE},
	seed = 7848,
	octaves = 4,
	persist = 0.7,
	lacunarity = 1.5,
}

-- Content IDs used with the voxel manipulator.
local c_air             = minetest.get_content_id("air")
local c_ignore          = minetest.get_content_id("ignore")
local c_desert_stone    = minetest.get_content_id("default:desert_stone")
local c_desert_cobble   = minetest.get_content_id("default:desert_cobble")
local c_bedrock         = minetest.get_content_id("bedrock:bedrock")
local c_sand            = minetest.get_content_id("default:sand")
local c_desert_sand     = minetest.get_content_id("default:desert_sand")
local c_water           = minetest.get_content_id("default:water_source")
local c_lava            = minetest.get_content_id("default:lava_source")

-- Externally located tables for performance.
local vm_data = {}
local biome_data = {}
local heightmap = {}

local noisemap1 = {}
local noisemap2 = {}
local noisemap3 = {}
local noisemap4 = {}
local noisemap5 = {}
local noisemap6 = {}
local noisemap7 = {}

local perlin1
local perlin2
local perlin3
local perlin4
local perlin5
local perlin6
local perlin7

jarkati.generate_realm = function(minp, maxp, seed)
	local nbeg = jarkati.REALM_START
	local nend = jarkati.REALM_END
	local slev = jarkati.SEA_LEVEL
	local lbeg = jarkati.REALM_START
	local lend = jarkati.LAVA_LEVEL

	-- Don't run for out-of-bounds mapchunks.
	if minp.y > nend or maxp.y < nbeg then
		return
	end

	-- Grab the voxel manipulator.
	-- Read current map data.
	local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
	vm:get_data(vm_data)
	local max_area = VoxelArea:new {MinEdge=emin, MaxEdge=emax}
	local min_area = VoxelArea:new {MinEdge=minp, MaxEdge=maxp}

	-- Actual emerged area should be bigger than the chunk we're generating!
	assert(emin.y < minp.y and emin.x < minp.x and emin.z < minp.z)
	assert(emax.y > maxp.y and emax.x > maxp.x and emax.z > maxp.z)

	local pr = PseudoRandom(seed + 351)
	local dpr = PseudoRandom(seed + 2891) -- For decoration placement probability.

	local x1 = maxp.x
	local y1 = maxp.y
	local z1 = maxp.z
	local x0 = minp.x
	local y0 = minp.y
	local z0 = minp.z

	-- Compute side lengths.
	local side_len_x = ((x1-x0)+1)
	local side_len_y = ((y1-y0)+1)
	local side_len_z = ((z1-z0)+1)
	local sides2D = {x=side_len_x, y=side_len_z}
	local sides3D = {x=side_len_x, y=side_len_z, z=side_len_y}
	local bp2d = {x=x0, y=z0}
	local bp3d = {x=x0, y=y0, z=z0}

	-- Get noisemaps.
	perlin1 = perlin1 or minetest.get_perlin_map(jarkati.noise1param2d, sides2D)
	perlin1:get2dMap_flat(bp2d, noisemap1)

	perlin2 = perlin2 or minetest.get_perlin_map(jarkati.noise2param2d, sides2D)
	perlin2:get2dMap_flat(bp2d, noisemap2)

	perlin3 = perlin3 or minetest.get_perlin_map(jarkati.noise3param2d, sides2D)
	perlin3:get2dMap_flat(bp2d, noisemap3)

	perlin4 = perlin4 or minetest.get_perlin_map(jarkati.noise4param2d, sides2D)
	perlin4:get2dMap_flat(bp2d, noisemap4)

	perlin5 = perlin5 or minetest.get_perlin_map(jarkati.noise5param2d, sides2D)
	perlin5:get2dMap_flat(bp2d, noisemap5)

	perlin6 = perlin6 or minetest.get_perlin_map(jarkati.noise6param3d, sides3D)
	perlin6:get3dMap_flat(bp3d, noisemap6)

	perlin7 = perlin7 or minetest.get_perlin_map(jarkati.noise7param3d, sides3D)
	perlin7:get3dMap_flat(bp3d, noisemap7)

	-- Localize commonly used functions for speed.
	local floor = math.floor
	local ceil = math.ceil
	local abs = math.abs
	local min = math.min
	local max = math.max

	-- Terrain height function (does not handle caves).
	local function height(z, x)
		-- Get index into 2D noise arrays.
		local nx = (x - x0)
		local nz = (z - z0)
		local ni2 = (side_len_z*nz+nx)
		-- Lua arrays start indexing at 1, not 0. Urrrrgh.
		ni2 = ni2 + 1

		local n1 = noisemap1[ni2]
		local n2 = noisemap2[ni2]
		local n3 = noisemap3[ni2]
		local n4 = noisemap4[ni2]
		local n5 = noisemap5[ni2]

		-- Calc base terrain height.
		local h = slev + (abs(n1) * 16)

		-- Modify the tableland noise parameter with this noise.
		n3 = n3 * abs(n4)
		n1 = n1 * abs(n5)

		-- If tableland noise over threshold, then flatten/invert terrain height.
		-- This generates "tablelands", buttes, etc.
		if n3 > -0.1 then
			h = slev + (n1 * 3)
		end
		-- If even farther over the threshold, then intensify/invert once again.
		-- This can result in apparent "land bridges" if the surrounding landscape
		-- is low, or it can create simple "canyons" if the surroundings are high.
		if n3 > 0.3 then
			h = slev - (n1 * 5)
		end
		if n3 > 0.6 then
			h = slev + (n1 * 8)
		end

		return h
	end

	local function cavern(x, y, z)
		local np = min_area:index(x, y, z)
		local n1 = noisemap6[np]
		local n2 = noisemap7[np]

		-- Get index into 2D noise arrays.
		local nx = (x - x0)
		local nz = (z - z0)
		local ni2 = (side_len_z*nz+nx)
		-- Lua arrays start indexing at 1, not 0. Urrrrgh.
		ni2 = ni2 + 1
		local n3 = abs(noisemap2[ni2])

		-- Reduce cavern noise at realm base.
		local d = (y - nbeg)
		if d < 0 then d = 0 end
		d = d / 16
		if d > 1 then d = 1 end
		if d < 0 then d = 0 end
		n1 = n1 * d

		-- Reduce cavern noise at surface level.
		local f = (slev - y)
		if f < 0 then f = 0 end
		f = f / 16
		if f > 1 then f = 1 end
		if f < 0 then f = 0 end
		n1 = n1 * f

		-- Expand cavern noise 50 meters below surface level, 10 meters above and below.
		local x = abs((slev - 50) - y)
		x = x * -1 + 16 -- Invert range.
		x = x / 16
		if x > 1 then x = 1 end
		if x < 0 then x = 0 end
		n1 = n1 * (1 + x)

		return (abs(n1) > 0.6 or (abs(n2) * (n3 * n3)) > 0.8)
	end

	-- Generic filler stone type.
	local c_stone = c_desert_stone

	-- First mapgen pass. Generate stone terrain shape, fill rest with air (critical).
	-- This also constructs the heightmap, which caches the height values.
	for z = z0, z1 do
		for x = x0, x1 do
			local ground = floor(height(z, x))
			heightmap[max_area:index(x, 0, z)] = ground

			local miny = (y0 - 0)
			local maxy = (y1 + 0)

			miny = max(miny, nbeg)
			maxy = min(maxy, nend)

			-- Flag set once a cave has carved away part of the surface in this column.
			-- Second flag set once the floor of the first cave is reached.
			-- Once the floor of the first cave is reached, the heightmap is adjusted.
			-- The heightmap is ONLY adjusted for caves that intersect with base ground level.
			local gc0 = false
			local gc1 = false

			-- First pass through column.
			-- Iterate downwards so we can detect when caves modify the surface height.
			for y = maxy, miny, -1 do
				local cave = cavern(x, y, z)
				local vp = max_area:index(x, y, z)
				local cid = vm_data[vp]

				-- Don't overwrite previously existing stuff (non-ignore, non-air).
				-- This avoids ruining schematics that were previously placed.
				if (cid == c_air or cid == c_ignore) then
					if cave then
						-- We've started carving a cave in this column.
						-- Don't bother flagging this unless the cave roof would be above ground level.
						if (y > ground and not gc0) then
							gc0 = true
						end

						if (y >= lbeg and y <= lend) then
							vm_data[vp] = c_lava
						else
							vm_data[vp] = c_air
						end
					else
						if y <= ground then
							-- We've finished carving a cave in this column.
							-- Adjust heightmap.
							-- But don't bother if cave floor would be above ground level.
							if (gc0 and not gc1) then
								heightmap[max_area:index(x, 0, z)] = y
								gc1 = true
							end

							vm_data[vp] = c_stone
						else
							if (y >= lbeg and y <= lend) then
								vm_data[vp] = c_lava
							else
								vm_data[vp] = c_air
							end
						end
					end
				end
			end -- End column loop.
		end
	end

	---[[
	-- Localize for speed.
	local all_biomes = jarkati.biomes
	local function biomes(biomes)
		local biome_count = 0
		for k, v in ipairs(biomes) do
			biome_count = biome_count + 1
			biome_data[biome_count] = v
		end
		return biome_data, biome_count -- Table is continuously reused.
	end

	-- Second mapgen pass. Generate topsoil layers.
	for z = z0, z1 do
		for x = x0, x1 do
			local miny = (y0 - 0)
			local maxy = (y1 + 0)

	    -- Get heightmap value at this location. This may have been adjusted by a surface cave.
			local ground = heightmap[max_area:index(x, 0, z)]

			-- Count of how many "surfaces" were detected (so far) while iterating down.
			-- 0 means we haven't found ground yet. 1 means found ground, >1 indicates a cave surface.
			local count = 0
			local depth = 0

			-- Get array and array-size of all biomes valid for this position.
			local vb, bc = biomes(all_biomes)

			-- Second pass through column. Iterate backwards for depth checking.
			for y = maxy, miny, -1 do
				if y >= nbeg and y <= nend then
					local vp0 = max_area:index(x, y, z)
					local vpu = max_area:index(x, (y - 1), z)

					if y <= ground then
						count = 1
						if vm_data[vp0] ~= c_air and vm_data[vpu] ~= c_air then
							depth = (ground - y) + 1
						else
							depth = 0
						end
					else
						count = 0
						depth = 0
					end

					-- Place topsoils & layers, etc. using current biome data.
					for i = 1, bc do
						-- Get biome data.
						local v = vb[i]

						if (count >= v.min_level and count <= v.max_level) then
							if (depth >= v.min_depth and depth <= v.max_depth) then
								vm_data[vp0] = v.cid
							end
						end
					end
				end
			end -- End column loop.
		end
	end
	--]]

	---[[
	-- Third mapgen pass. Generate bedrock layer overwriting everything else (critical).
	if not (y1 < nbeg or y0 > (nbeg + 10)) then
		for z = z0, z1 do
			for x = x0, x1 do
				-- Randomize height of the bedrock a bit.
				local bedrock = (nbeg + pr:next(5, pr:next(6, 7)))
				local miny = max(y0, nbeg)
				local maxy = min(y1, bedrock)

				-- Third pass through column.
				for y = miny, maxy do
					local vp = max_area:index(x, y, z)
					vm_data[vp] = c_bedrock
				end -- End column loop.
			end
		end
	end
	--]]

	-- Finalize voxel manipulator.
	vm:set_data(vm_data)
	minetest.generate_ores(vm)
	vm:write_to_map(false)

	-- Not needed to do this, it will be done during the "mapfix" call.
	--vm:update_liquids()

	---[[
	-- Localize for speed.
	local all_decorations = jarkati.decorations
	local decopos = {x=0, y=0, z=0}
	local set_node = minetest.set_node
	local get_node = minetest.get_node
	local put_schem = minetest.place_schematic
	local deconode = {name="", param2=0}

	local function decorate(v, x, y, z, d)
		-- Don't place decorations outside chunk boundaries.
		-- (X and Z are already checked.)
		if (y < y0 or y > y1) then
			return
		end
		if (y > v.y_max or y < v.y_min) then
	    return
		end

		decopos.x = x
		decopos.y = y
		decopos.z = z

		if v.spawn_by then
			if not minetest.find_node_near(decopos, (v.radius + 1), v.spawn_by) then
				return
			end
		end

		-- Validate the ground/ceiling surface.
		do
	    local x1 = decopos.x - v.radius
	    local x2 = decopos.x + v.radius
	    local z1 = decopos.z - v.radius
	    local z2 = decopos.z + v.radius
	    local nn

	    -- All must be a valid floor/ceiling node!
	    decopos.y = decopos.y + d
	    for x = x1, x2 do
				for z = z1, z2 do
					decopos.x = x
					decopos.z = z
					nn = get_node(decopos).name
					-- Always check to make sure we're not air, here.
					-- This avoids spawning decorations on ground that was carved away by the cavegen.
					if nn == "air" or nn == "ignore" then
						return
					end
					-- If decoration requires specific node type, check if we have it.
					if v.place_on then
						local hs = false
						for t, j in ipairs(v.place_on) do
							if j == nn then
								hs = true
								break
							end
						end
						if not hs then
							return
						end
					end
				end
	    end

	    -- All must be empty air!
	    decopos.y = decopos.y - d
	    for x = x1, x2 do
				for z = z1, z2 do
					decopos.x = x
					decopos.z = z
					nn = get_node(decopos).name
					if nn ~= "air" then
						return
					end
				end
	    end

	    -- Back to ground. Replace ground surface!
	    if v.replace_surface then
				decopos.y = decopos.y + d
				for x = x1, x2 do
					for z = z1, z2 do
						decopos.x = x
						decopos.z = z
						deconode.name = v.replace_surface[dpr:next(1, #v.replace_surface)]
						deconode.param2 = 0
						set_node(decopos, deconode)

						if v.custom_func then
							v.custom_func(decopos)
						end
					end
				end
	    end

	    -- Reset deco coordinates.
	    decopos.x = x
	    decopos.y = y
	    decopos.z = z
		end

		if v.nodes then
			deconode.name = v.nodes[dpr:next(1, #v.nodes)]
			deconode.param2 = v.param2[dpr:next(1, #v.param2)]
			set_node(decopos, deconode)

			if v.custom_func then
				v.custom_func(decopos)
			end
		elseif v.schematic then
	    decopos.y = decopos.y + v.place_offset_y
	    put_schem(decopos, v.schematic, v.rotation, v.replacements, v.force_placement, v.flags)
	    decopos.y = decopos.y - v.place_offset_y

			if v.custom_func then
				v.custom_func(decopos)
			end
		end
	end

	-- Fourth mapgen pass. Generate decorations using highlevel placement functions.
	-- Note: we still read the voxelmanip data! But we can't modify it.
	for z = z0, z1 do
		for x = x0, x1 do

			for k, v in ipairs(all_decorations) do

				if not (y0 > v.y_max or y1 < v.y_min) then
					if dpr:next(1, v.probability) == 1 then
						-- Don't bother with ground-level placement if 'all_floors' was specified.
						if (v.ground_level and not v.all_floors) then
							local g0 = heightmap[max_area:index(x, 0, z)]
							local g1 = (g0 + 1)
							decorate(v, x, g1, z, -1)
						end

						if (v.all_floors or v.all_ceilings) then
							local miny = (y0 - 1)
							local maxy = (y1 + 1)
							for y = maxy, miny, -1 do
								local vpa = max_area:index(x, y, z)
								local vpu = max_area:index(x, (y - 1), z)

								local cida = vm_data[vpa]
								local cidu = vm_data[vpu]

								if v.all_floors then
									if (cida == c_air and cidu ~= c_air) then
										decorate(v, x, y, z, -1)
									end
								end
								if v.all_ceilings then
									if (cida ~= c_air and cidu == c_air) then
										decorate(v, x, (y - 1), z, 1)
									end
								end
							end
						end
					end
				end

			end
		end
	end
	--]]

	-- Correct lighting and liquid flow.
	-- This works, but for some reason I have to grab a new voxelmanip object.
	-- I can't seem to fix lighting using the original mapgen object?
	-- Seems to require the full overgenerated mapchunk size if non-singlenode.
	mapfix.work(emin, emax)
end



if not jarkati.registered then
	-- Register the mapgen callback.
	minetest.register_on_generated(function(...)
		jarkati.generate_realm(...)
	end)

	oregen.register_ore({
		ore_type = "scatter",
		ore = "default:desert_stone_with_copper",
		wherein = {"default:desert_stone"},
		clust_scarcity = 6*6*6,
		clust_num_ores = 4,
		clust_size = 3,
		y_min = 3600,
		y_max = 3900,
	})

	oregen.register_ore({
		ore_type = "scatter",
		ore = "default:desert_stone_with_copper",
		wherein = {"default:desert_stone"},
		clust_scarcity = 15*15*15,
		clust_num_ores = 27,
		clust_size = 6,
		y_min = 3600,
		y_max = 3700,
	})

	oregen.register_ore({
		ore_type = "scatter",
		ore = "default:desert_stone_with_iron",
		wherein = {"default:desert_stone"},
		clust_scarcity = 10*10*10,
		clust_num_ores = 4,
		clust_size = 3,
		y_min = 3600,
		y_max = 3900,
	})

	oregen.register_ore({
		ore_type = "scatter",
		ore = "default:desert_stone_with_iron",
		wherein = {"default:desert_stone"},
		clust_scarcity = 24*24*24,
		clust_num_ores = 27,
		clust_size = 6,
		y_min = 3600,
		y_max = 3700,
	})

	oregen.register_ore({
		ore_type = "scatter",
		ore = "default:desert_stone_with_diamond",
		wherein = {"default:desert_stone"},
		clust_scarcity = 17*17*17,
		clust_num_ores = 4,
		clust_size = 3,
		y_min = 3600,
		y_max = 3900,
	})

	oregen.register_ore({
		ore_type = "scatter",
		ore = "default:desert_stone_with_diamond",
		wherein = {"default:desert_stone"},
		clust_scarcity = 15*15*15,
		clust_num_ores = 6,
		clust_size = 3,
		y_min = 3600,
		y_max = 3700,
	})

	oregen.register_ore({
		ore_type = "scatter",
		ore = "default:desert_stone_with_coal",
		wherein = {"default:desert_stone"},
		clust_scarcity = 8*8*8,
		clust_num_ores = 8,
		clust_size = 3,
		y_min = 3600,
		y_max = 3900,
	})

	oregen.register_ore({
		ore_type = "scatter",
		ore = "pm:quartz_ore",
		wherein = {"default:desert_sand"},
		clust_scarcity = 8*8*8,
		clust_num_ores = 8,
		clust_size = 3,
		y_min = 3600,
		y_max = 3900,
	})

	local c = "jarkati:core"
	local f = jarkati.modpath .. "/init.lua"
	reload.register_file(c, f, false)

	jarkati.registered = true
end
